using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Security.Cryptography;
using System.Text;

namespace CollabApp.Domain.Entities.Auth;

/// <summary>
/// 用户实体 - 存储用户基本信息和账户状态
/// 继承BaseEntity，支持软删除功能
/// </summary>
[Table("users")]
public class User : BaseEntity
{
    // ============ 构造函数 ============
    
    /// <summary>
    /// 无参构造函数 - EF Core 要求
    /// </summary>
    private User() { }

    /// <summary>
    /// 私有构造函数，仅限工厂方法调用
    /// </summary>
    /// <param name="username">用户名</param>
    /// <param name="passwordHash">密码哈希值</param>
    /// <param name="passwordSalt">密码盐值</param>
    /// <param name="nickname">游戏昵称</param>
    private User(string username, string passwordHash, string passwordSalt, string nickname)
    {
        Username = username;
        PasswordHash = passwordHash;
        PasswordSalt = passwordSalt;
        Nickname = nickname;
        Status = UserStatus.Active;
        TokenStatus = TokenStatus.None;
        RememberMe = false;
    }

    // ============ 基本信息字段 ============

    /// <summary>
    /// 用户名 - 登录用，唯一且不可重复
    /// </summary>
    [Required]
    [MaxLength(50)]
    [Column("username")]
    public string Username { get; private set; } = string.Empty;

    /// <summary>
    /// 密码哈希值 - 存储经过哈希处理的密码，不存储明文
    /// </summary>
    [Required]
    [MaxLength(255)]
    [Column("password_hash")]
    public string PasswordHash { get; private set; } = string.Empty;

    /// <summary>
    /// 密码盐值 - 用于增强密码安全性，防止彩虹表攻击
    /// </summary>
    [Required]
    [MaxLength(255)]
    [Column("password_salt")]
    public string PasswordSalt { get; private set; } = string.Empty;

    /// <summary>
    /// 游戏昵称 - 在游戏中显示的名称
    /// </summary>
    [Required]
    [MaxLength(50)]
    [Column("nickname")]
    public string Nickname { get; private set; } = string.Empty;

    /// <summary>
    /// 头像URL - 用户头像图片的存储地址
    /// </summary>
    [MaxLength(255)]
    [Column("avatar_url")]
    public string? AvatarUrl { get; private set; }

    /// <summary>
    /// 隐私设置 - JSON格式存储用户的隐私偏好配置
    /// </summary>
    [Column("privacy_settings", TypeName = "json")]
    public string? PrivacySettings { get; private set; }

    /// <summary>
    /// 最后登录时间 - 记录用户最近一次登录的时间
    /// </summary>
    [Column("last_login_at")]
    public DateTime? LastLoginAt { get; private set; }

    /// <summary>
    /// 账户状态 - 正常,封禁等状态
    /// </summary>
    [Column("status")]
    public UserStatus Status { get; private set; } = UserStatus.Active;

    // ============ 双Token认证字段 ============

    /// <summary>
    /// 访问令牌 - 短期有效的身份验证令牌
    /// 用于API请求的身份验证，通常有效期较短（如15-30分钟）
    /// </summary>
    [MaxLength(512)]
    [Column("access_token")]
    public string? AccessToken { get; private set; }

    /// <summary>
    /// 刷新令牌 - 长期有效的令牌，用于刷新访问令牌
    /// 安全性更高，有效期较长（如7-30天），用于获取新的访问令牌
    /// </summary>
    [MaxLength(512)]
    [Column("refresh_token")]
    public string? RefreshToken { get; private set; }

    /// <summary>
    /// 访问令牌过期时间 - AccessToken的有效期
    /// </summary>
    [Column("access_token_expires_at")]
    public DateTime? AccessTokenExpiresAt { get; private set; }

    /// <summary>
    /// 刷新令牌过期时间 - RefreshToken的有效期
    /// </summary>
    [Column("refresh_token_expires_at")]
    public DateTime? RefreshTokenExpiresAt { get; private set; }

    /// <summary>
    /// 记住登录状态 - 是否启用长期登录功能
    /// 启用时RefreshToken有效期会延长
    /// </summary>
    [Column("remember_me")]
    public bool RememberMe { get; private set; } = false;

    /// <summary>
    /// 令牌状态 - 活跃、已吊销、已过期等状态
    /// </summary>
    [Column("token_status")]
    public TokenStatus TokenStatus { get; private set; } = TokenStatus.None;

    /// <summary>
    /// 最后活跃时间 - 用户最后一次使用令牌的时间
    /// </summary>
    [Column("last_activity_at")]
    public DateTime? LastActivityAt { get; private set; }

    /// <summary>
    /// 登录设备信息 - JSON格式存储设备类型、IP地址等信息
    /// </summary>
    [Column("device_info", TypeName = "json")]
    public string? DeviceInfo { get; private set; }

    /// <summary>
    /// 令牌吊销原因 - 令牌被吊销时的原因说明
    /// </summary>
    [MaxLength(200)]
    [Column("token_revoked_reason")]
    public string? TokenRevokedReason { get; private set; }

    /// <summary>
    /// 令牌吊销时间 - 令牌被手动吊销的时间
    /// </summary>
    [Column("token_revoked_at")]
    public DateTime? TokenRevokedAt { get; private set; }

    // ============ 导航属性 ============
    
    /// <summary>
    /// 用户统计信息 - 一对一关系
    /// </summary>
    public virtual UserStatistics? Statistics { get; set; }
    
    /// <summary>
    /// 用户创建的房间列表 - 一对多关系，用户作为房主的房间
    /// </summary>
    public virtual ICollection<Room.Room> OwnedRooms { get; set; } = new List<Room.Room>();
    
    /// <summary>
    /// 用户参与的房间记录 - 一对多关系，用户加入过的所有房间
    /// </summary>
    public virtual ICollection<Room.RoomPlayer> RoomPlayers { get; set; } = new List<Room.RoomPlayer>();
    
    /// <summary>
    /// 用户游戏记录 - 一对多关系，用户参与过的所有游戏
    /// </summary>
    public virtual ICollection<Game.GamePlayer> GamePlayers { get; set; } = new List<Game.GamePlayer>();
    
    /// <summary>
    /// 用户通知列表 - 一对多关系，用户收到的所有通知
    /// </summary>
    public virtual ICollection<Notification> Notifications { get; set; } = new List<Notification>();

    // ============ 工厂方法 ============

    /// <summary>
    /// 创建新用户 - 工厂方法（使用明文密码）
    /// </summary>
    /// <param name="username">用户名</param>
    /// <param name="plainPassword">明文密码</param>
    /// <param name="nickname">游戏昵称</param>
    /// <returns>新用户实例</returns>
    public static User Create(string username, string plainPassword, string nickname)
    {
        if (string.IsNullOrWhiteSpace(username))
            throw new ArgumentException("用户名不能为空", nameof(username));
        if (string.IsNullOrWhiteSpace(plainPassword))
            throw new ArgumentException("密码不能为空", nameof(plainPassword));
        if (string.IsNullOrWhiteSpace(nickname))
            throw new ArgumentException("昵称不能为空", nameof(nickname));

        var (hash, salt) = CreatePasswordHash(plainPassword);
        return new User(username, hash, salt, nickname);
    }

    // ============ 业务方法 ============

    /// <summary>
    /// 更新用户信息
    /// </summary>
    /// <param name="nickname">新昵称</param>
    /// <param name="avatarUrl">头像URL</param>
    public void UpdateProfile(string nickname, string? avatarUrl = null)
    {
        if (string.IsNullOrWhiteSpace(nickname))
            throw new ArgumentException("昵称不能为空", nameof(nickname));

        Nickname = nickname;
        AvatarUrl = avatarUrl;
    }

    /// <summary>
    /// 更新密码 - 使用明文密码，自动生成新的盐值和哈希
    /// </summary>
    /// <param name="newPassword">新明文密码</param>
    public void UpdatePassword(string newPassword)
    {
        if (string.IsNullOrWhiteSpace(newPassword))
            throw new ArgumentException("密码不能为空", nameof(newPassword));

        var newSalt = GenerateSalt();
        var newHash = HashPassword(newPassword, newSalt);
        
        PasswordSalt = newSalt;
        PasswordHash = newHash;
    }

    /// <summary>
    /// 设置访问令牌
    /// </summary>
    /// <param name="accessToken">访问令牌</param>
    /// <param name="refreshToken">刷新令牌</param>
    /// <param name="accessExpires">访问令牌过期时间</param>
    /// <param name="refreshExpires">刷新令牌过期时间</param>
    /// <param name="rememberMe">是否记住登录</param>
    /// <param name="deviceInfo">设备信息</param>
    public void SetTokens(string accessToken, string refreshToken, DateTime accessExpires, 
    DateTime refreshExpires, bool rememberMe = false, string? deviceInfo = null)
    {
        AccessToken = accessToken;
        RefreshToken = refreshToken;
        AccessTokenExpiresAt = accessExpires;
        RefreshTokenExpiresAt = refreshExpires;
        RememberMe = rememberMe;
        TokenStatus = TokenStatus.Active;
        LastActivityAt = DateTime.UtcNow;
        LastLoginAt = DateTime.UtcNow;
        DeviceInfo = deviceInfo;
    }

    /// <summary>
    /// 刷新访问令牌
    /// </summary>
    /// <param name="newAccessToken">新访问令牌</param>
    /// <param name="newAccessExpires">新访问令牌过期时间</param>
    public void RefreshAccessToken(string newAccessToken, DateTime newAccessExpires)
    {
        AccessToken = newAccessToken;
        AccessTokenExpiresAt = newAccessExpires;
        TokenStatus = TokenStatus.Active;
        LastActivityAt = DateTime.UtcNow;
    }

    /// <summary>
    /// 吊销令牌
    /// </summary>
    /// <param name="reason">吊销原因</param>
    public void RevokeTokens(string? reason = null)
    {
        AccessToken = null;
        RefreshToken = null;
        AccessTokenExpiresAt = null;
        RefreshTokenExpiresAt = null;
        TokenStatus = TokenStatus.Revoked;
        TokenRevokedReason = reason;
        TokenRevokedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// 更新活跃时间
    /// </summary>
    public void UpdateActivity()
    {
        LastActivityAt = DateTime.UtcNow;
    }

    /// <summary>
    /// 封禁用户
    /// </summary>
    public void Ban()
    {
        Status = UserStatus.Banned;
        RevokeTokens("用户被封禁");
    }

    /// <summary>
    /// 解封用户
    /// </summary>
    public void Unban()
    {
        Status = UserStatus.Active;
    }

    // ============ 密码安全方法 ============

    /// <summary>
    /// 生成一个随机的密码盐，返回 Base64 字符串
    /// </summary>
    /// <returns>Base64 编码的盐字符串</returns>
    private static string GenerateSalt()
    {
        var bytes = new byte[16];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes);
    }

    /// <summary>
    /// 使用 PBKDF2 算法对密码和盐进行加密，返回哈希后的 Base64 字符串
    /// </summary>
    /// <param name="password">明文密码</param>
    /// <param name="salt">Base64 编码的盐</param>
    /// <returns>Base64 编码的哈希密码</returns>
    private static string HashPassword(string password, string salt)
    {
        var saltBytes = Convert.FromBase64String(salt);
        using var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000, HashAlgorithmName.SHA256);
        var hash = pbkdf2.GetBytes(32); // 256位
        return Convert.ToBase64String(hash);
    }

    /// <summary>
    /// 验证密码是否正确
    /// </summary>
    /// <param name="password">要验证的密码</param>
    /// <param name="storedHash">存储的哈希密码</param>
    /// <param name="storedSalt">存储的盐值</param>
    /// <returns>密码是否正确</returns>
    public static bool VerifyPassword(string password, string storedHash, string storedSalt)
    {
        if (string.IsNullOrWhiteSpace(password))
            return false;
        if (string.IsNullOrWhiteSpace(storedHash))
            return false;
        if (string.IsNullOrWhiteSpace(storedSalt))
            return false;

        try
        {
            var computedHash = HashPassword(password, storedSalt);
            return computedHash.Equals(storedHash, StringComparison.Ordinal);
        }
        catch
        {
            // 如果发生任何异常（如盐值格式错误），返回false
            return false;
        }
    }

    /// <summary>
    /// 验证输入的明文密码是否与当前用户密码一致
    /// </summary>
    /// <param name="plainPassword">明文密码</param>
    /// <returns>密码是否正确</returns>
    public bool VerifyPassword(string plainPassword)
    {
        if (string.IsNullOrWhiteSpace(plainPassword))
            return false;

        var computedHash = HashPassword(plainPassword, PasswordSalt);
        return computedHash.Equals(PasswordHash, StringComparison.Ordinal);
    }

    /// <summary>
    /// 创建用户时生成密码哈希和盐值 - 便捷方法
    /// </summary>
    /// <param name="password">原始密码</param>
    /// <returns>包含哈希值和盐值的元组</returns>
    public static (string Hash, string Salt) CreatePasswordHash(string password)
    {
        if (string.IsNullOrWhiteSpace(password))
            throw new ArgumentException("密码不能为空", nameof(password));

        var salt = GenerateSalt();
        var hash = HashPassword(password, salt);
        return (hash, salt);
    }

}

/// <summary>
/// 令牌状态枚举 - 定义用户认证令牌的各种状态
/// </summary>
public enum TokenStatus
{
    /// <summary>
    /// 无令牌状态 - 用户未登录或令牌已清空
    /// </summary>
    None,
    
    /// <summary>
    /// 活跃状态 - 令牌正常有效
    /// </summary>
    Active,
    
    /// <summary>
    /// 已过期 - 令牌已超过有效期
    /// </summary>
    Expired,
    
    /// <summary>
    /// 已吊销 - 令牌被用户或系统主动撤销
    /// </summary>
    Revoked,
    
    /// <summary>
    /// 需要刷新 - AccessToken过期，需要使用RefreshToken刷新
    /// </summary>
    NeedsRefresh
}

/// <summary>
/// 用户状态枚举
/// </summary>
public enum UserStatus
{
    /// <summary>
    /// 正常状态 - 正常可用
    /// </summary>
    Active,
    
    /// <summary>
    /// 封禁状态 - 账户被管理员封禁
    /// </summary>
    Banned
}
